/**************************************************************************************
* Player.java
* 
*************************************************************************************/
package creid.mythos.engine;

import java.io.Serializable;

import creid.mythos.shared.PlayerStatus;

public class Player extends Actor implements Serializable, PlayerStatus
{
	//Constants/////////////////////////////////////////////////////////////////////////

	private static final long serialVersionUID = 5300550493672090195L;

	public static final String WEAPON_ATTACK_VERB = "hits";
	//outfit (armor or clothing), hat/helm, weapon(including a shield), swap (slot for swap weapon), Tool (torch/pick/whatever), and three accessories.

	public static final int MAINDHAND_SLOT = 0;
	public static final int SWAP_SLOT = 1;
	public static final int BODY_SLOT = 2;
	public static final int HEAD_SLOT = 3;
	public static final int TRINKET1_SLOT = 4;
	public static final int TRINKET2_SLOT = 5;
	public static final int TRINKET3_SLOT = 6;
	public static final int NUM_SLOTS = 7;
	
	public static final String[] EQUIP_NAMES = {
		"Wielding:    ",
		"Swap:        ",
		"Wearing:     ",
		"On Head:     ",
		"Trinket 1:   ",
		"Trinket 2:   ",
		"Trinket 3:   ", 
		"Ammo:        " 
	};
	
	public static final String[] EMPTY_NAME = {
		"(fists (+0))",
		"(nothing)",
		"(clothes [+0])",
		"(nothing [+0])",
		"(nothing)",
		"(nothing)",
		"(nothing)",
		"(nothing)"
	};
	
	public static final String[] EQUIP_VERB = {
		"wields",
		"readies",
		"wears",
		"wears",
		"puts on",
		"puts on",
		"puts on",
		"loads"
	};
	
	public static final String[] UNEQUIP_VERB = {
		"puts away",
		"puts away",
		"takes off",
		"takes off",
		"puts away",
		"puts away",
		"puts away",
		"unloads"
	};
	
	//Attributes////////////////////////////////////////////////////////////////////////
	
	private String role;
	
	//Equipment
	private ItemSlot[] equipment;
	
	//Experience
	private int experience;
	private int xpPenalty;
	
	//Constructors//////////////////////////////////////////////////////////////////////

	public Player(String name, Actor background)
	{
		super(background);
		
		role = getSingularName();
		setSingularName(name);
		
		xpPenalty = background.getAwareness();
		
		role = background.getSingularName();
		setAmount(Entity.UNIQUE);
		
		//TODO: Add a line in backgrounds with role-specific starting items
		equipment = new ItemSlot[NUM_SLOTS];
		equipment[MAINDHAND_SLOT] = new ItemSlot(ItemSlot.WIELD);
		equipment[SWAP_SLOT] = new ItemSlot(ItemSlot.SWAP);
		equipment[BODY_SLOT] = new ItemSlot(Item.I_OUTFIT);
		equipment[HEAD_SLOT] = new ItemSlot(Item.I_HEADGEAR);
		equipment[TRINKET1_SLOT] = new ItemSlot(Item.I_TRINKET);
		equipment[TRINKET2_SLOT] = new ItemSlot(Item.I_TRINKET);
		equipment[TRINKET3_SLOT] = new ItemSlot(Item.I_TRINKET);
		
		stowItem(Mythos.genItem(5));
		stowItem(Mythos.genItem(9));
		stowItem(Mythos.genItem(12));
		stowItem(Mythos.genItem(18));
		stowItem(Mythos.genItem(21));
		
		calcStats();
	}

	//Methods///////////////////////////////////////////////////////////////////////////

	int getXPPenalty()
	{
		return xpPenalty;
	}
	
	@Override
	public String getName()
	{
		return getSingularName();
	}

	@Override
	public String getRole()
	{
		return role;
	}

	@Override
	public String getXP()
	{
		// Experience needed is square of next level
		return experience + "/" + ((1+getLevel()) * (1+getLevel()));
	}

	public Item getEquipment(int slot)
	{
		if (slot < 0 || slot >= equipment.length)
			return null;
		
		return equipment[slot].getEquipment();
	}
	
	public Item removeEquipment(int slot)
	{
		Item removed = equipment[slot].removeEquipment();;
		
		if (removed != null)
		{
			if (slot == BODY_SLOT || slot == HEAD_SLOT)
			{
				if (removed.getPower() > brawn)
				{
					Mythos.logger.logMessage(getName(DEFINITE) + "'s movements are no longer encumbered.");
				}
			}
			else if (slot == MAINDHAND_SLOT)
			{
				if (removed.getPower() > brawn)
					Mythos.logger.logMessage(getName(DEFINITE) + " is relieved to put down such a heavy weapon");
			}
			
			//If removed weapon has a launcher with ammo, unload the ammo
			if (removed.getType() == Item.I_GUN && removed.hasProperty("loaded"))
			{
				Item unloaded = Mythos.genItem(removed.getProperty("ammo"));
				unloaded.setAmount(removed.getProperty("loaded"));
				removed.setProperty("loaded", 0);
				
				stowItem(unloaded);
			}
			
			calcStats();
		}
		
		return removed;
	}
	
	public String[] getEquipmentMenu()
	{
		String[] menu = new String[equipment.length];
		
		for (int i = 0; i < menu.length; i++)
		{
			if (equipment[i].hasEquipment())
				menu[i] = EQUIP_NAMES[i] + equipment[i].getEquipment().getName(INDEFINITE);
			else
				menu[i] = EQUIP_NAMES[i] + EMPTY_NAME[i];
		}
		
		return menu;
	}
	
	public boolean canEquip(Item item, int slot)
	{
		if (slot < 0 || slot >= equipment.length)
			return false;
		else
			return equipment[slot].canEquip(item);
	}
	
	public boolean equipItem(Item item, int slot)
	{
		if (!canEquip(item, slot))
			return false;
		
		equipment[slot].setEquipment(item);
		
		//Warn player of poorly suited equipment
		if (item != null)
		{
			switch(slot)
			{
			case MAINDHAND_SLOT:
				if (item.getPower() > brawn)
				{
					int penalty = -2 * (item.getPower() - brawn);
					Mythos.logger.logMessage(getName(DEFINITE) + " has trouble lifting such a heavy weapon (" + penalty + " penalty)");
				}
				break;
			case BODY_SLOT:
			case HEAD_SLOT:
					if (item.getPower() > brawn)
						Mythos.logger.logMessage("The weight of " + getName(DEFINITE) + "'s armor encumbers their movement "
								+ "(Lost Grace bonus to armor, cannot swim or jump)");
				break;
			}
		}
		
		calcStats();
		
		return true;
	}
	
	@Override
	public String getAttackStatus()
	{
		return "(" + attack1.getPower() + ")";
	}

	@Override
	public int getCondition()
	{
		if (getHealth() < 1)
			return PlayerStatus.CON_DYING;
		else if (3 * getHealth() <= getMaxHealth())
			return PlayerStatus.CON_LOW_HEALTH;
		else if (3 * getHealth() <= 2*getMaxHealth())
			return PlayerStatus.CON_INJURED;
		else
			return PlayerStatus.CON_HEALTHY;
	}
	
	@Override
	public String getHealthStatus()
	{
		return getHealth() + "/" + getMaxHealth();
	}

	@Override
	public String getDefenseStatus()
	{
		return "[" + defense + "," + willpower + "]";
	}
	
	/**
	 * Move
	 * Move relative to current position
	 * @param dx
	 * @param dy
	 * @return success or cancel
	 */

	public boolean move(int dx, int dy)
	{
		Cell to = getLocation().getMap().getRelativeLocation(getLocation(), dx, dy);
		boolean success = false;
		
		if (dx == 0 && dy == 0)
			return super.move(to);

		Cell from = getLocation();
			
		//If trying to move into a cell occupied by an ally, swap places instead
		if (to.isPassable(walks, swims, flies) && to.isOccupied() && to.getOccupant().getFaction() == Actor.PLAYER_FACTION
				//Occupant must be able to swap places with you
				&& getLocation().isPassable(to.getOccupant().canWalk(), to.getOccupant().canSwim(), to.getOccupant().canFly()))
		{
			Mythos.logger.logMessage(getName(Entity.INDEFINITE) + " swaps places with" + to.getOccupant().getName(Entity.DEFINITE));
			
			//From cell
			Actor other = to.getOccupant();
			
			//Clear the cells
			from.setOccupant(null);
			to.setOccupant(null);
			
			//Move the occupant
			other.setLocation(from);
			from.setOccupant(other);
			
			//Then ourself
			to.setOccupant(this);
			setLocation(to);
			
			success = super.move(getLocation());
		}
		else
			success = super.move(to);
		
		//If something interesting is on our new cell, tell the player about it
		if (success)
		{
			//Don't spam player with messages
			if (from != getLocation() && getLocation().hasTreasure())
			{
				Mythos.logger.logMessage(getName() + " sees " + getLocation().getTreasure().getName(INDEFINITE)); 
			}
		}
		
		return success;
	}
	
	/**
	 * For the player, use attack1 (unarmed attack) if attack2 (weapon attack) is null,
	 * but always use a weapon if we have one
	 */
	@Override
	protected Item getWeapon()
	{
		return attack1;
	}
	
	@Override 
	public boolean meleeAttack(Actor target)
	{
		//Set target
		getLocation().getMap().setTarget(target);
		
		return super.meleeAttack(target);
	}
	
	@Override
	//Here we want to tell the player how many items they have
	public boolean pickupItem()
	{
		Item[] oldItemList = new Item[listInventory().length];
		int index = 0;
		for (Item item: listInventory())
		{
			oldItemList[index] = new Item(item);
			index++;
		}
		
		boolean success = super.pickupItem();
		
		if (success)
		{
			Item[] newInv = listInventory();
			
			int i = 0;
			if (oldItemList != null && oldItemList.length > 0)
			{
				if (newInv.length > oldItemList.length)
				{
					//TODO Hackish - compare names. Need a comparator implementation for entities to compare them generally
					while (i < oldItemList.length && newInv[i].getName(DEFINITE).equals(oldItemList[i].getName(DEFINITE))) i++;
				}
				else
				{
					while (i < oldItemList.length && newInv[i].getAmount() == oldItemList[i].getAmount()) i++;
				}
			}
			Mythos.logger.logMessage("You have " + seeItem(i).getName(INDEFINITE));
		}
		else Mythos.logger.logMessage("You see nothing to pick up here");
		
		return success;
	}
	
	@Override
	void calcAttacks()
	{
		if (equipment != null && equipment[MAINDHAND_SLOT].hasEquipment())
		{
			Item weapon = equipment[MAINDHAND_SLOT].getEquipment();
			
			int brawnMod = brawn - weapon.getPower();
			
			//Penalized 2 per strength shortfall
			if (brawnMod < 0)
				brawnMod *= 2;
			else
				brawnMod /= 2;
			
			attack1.setPower(weapon.getPower() + brawnMod);
			
			attack1.setUseVerb(WEAPON_ATTACK_VERB);
			attack1.setElement(weapon.getElement());
		}
		else
		{
			attack1.setPower(Math.max(1, brawn/2));
			attack1.setUseVerb("punches");
			attack1.setElement(Entity.PHYSICAL);
		}
	}
	
	@Override
	void calcDefenses()
	{
		defense = 0;
		
		//Body armor
		if (equipment != null && equipment[BODY_SLOT].hasEquipment())
		{
			defense += equipment[BODY_SLOT].getEquipment().getPower();
		}
		
		//headgear
		if (equipment != null && equipment[HEAD_SLOT].hasEquipment())
		{
			defense += equipment[HEAD_SLOT].getEquipment().getPower();
		}
		
		//If armor is too heavy, we don't get our grace added to defense
		if (!isEncumbered())
		{
			defense += grace;
		}
	}
	
	/**
	 * swapWeapons
	 * Switch out current weapon with swap
	 */
	void swapWeapons()
	{
		Item temp = removeEquipment(MAINDHAND_SLOT);
		equipItem(equipment[SWAP_SLOT].removeEquipment(), MAINDHAND_SLOT);
		equipItem(temp, SWAP_SLOT);
		calcStats();
	}
	
	private int getEquipmentWeight(int slot)
	{
		int weight = 0;
		if (equipment[BODY_SLOT].hasEquipment())
		{
			weight = equipment[BODY_SLOT].getEquipment().getPower();
			
			if (equipment[BODY_SLOT].getEquipment().hasProperty("weight"))
				weight += equipment[BODY_SLOT].getEquipment().getProperty("weight");
		}
		
		return weight;
	}
	
	@Override
	public boolean isEncumbered()
	{
		//Is our equipment too heavy?
		if (equipment != null)
		{
			int carry = brawn * 2;
			
			carry -= getEquipmentWeight(BODY_SLOT);
			carry -= getEquipmentWeight(HEAD_SLOT);
			
			if (carry < 0)
				return true;
		}
		
		//Are we stuck or just really slow?
		if (hasProperty("lumbering") || hasProperty("immobilized"))
			return true;
		else
			return false;
	}
	
	@Override
	boolean shoot(Item shot, Cell target)
	{
		boolean success = super.shoot(shot, target);
		
		//Check success to ensure we have the proper weapon and properties
		if (success)
		{
			if (!equipment[Player.MAINDHAND_SLOT].getEquipment().hasProperty("loaded") && equipment[Player.MAINDHAND_SLOT].getEquipment().getProperty("capacity") > 1)
				Mythos.logger.logMessage("Out of ammo!");
		}
		
		return success;
	}
}
